﻿/*
Copyright (c) 2011, Pawel Szczurek
All rights reserved.


Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:


Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without
specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Alice.Script;

namespace Alice.VM
{
    /// <summary>
    /// class that can execute 32b program
    /// </summary>
    public static class ExecutionUnit
    {

        public class ExecutionUnitException : Exception
        {
            public ExecutionUnitException(string msg, int IP, string stack) : base(msg) { }
        }

        public static void Execute(CallStack cs)
        {
            Debug.Assert(cs.CallFinished != true);

            do
            {
                ExecuteNextInstruction(cs);
            } while (!cs.CallFinished && !cs.Sleep);

            if (cs.CallFinished)
            {
                Debug.Assert(cs.Sleep == false && cs.SleepState.GetValueType() == Alice.Script.ValueType.Undefined);
            }
        }

        #region private

        static void ExecuteNextInstruction(CallStack cs)
        {
            Debug.Assert(!cs.CallFinished);
            if (cs.SleepState.GetValueType() != Alice.Script.ValueType.Undefined) { Debug.Assert(cs.Sleep == true); }

            //load code
            OPCodes code = (OPCodes)cs.CurrentProgram.ProgramCode[cs.CurrentIP++];
        //    m_opcodeStats[(byte)code]++;

            if (cs.Sleep)
            {
                Debug.Assert(code == OPCodes.Call);
            }

            switch (code)
            {
                case OPCodes.Nop: break;

               // case OPCodes.Wait: m_threadSleep = true; break;

              /*  case OPCodes.Exit:
                    {
                        m_threadSleep = true;
                        m_programFinished = true;

                        EndProgramClean();
                    }
                    break;*/

                case OPCodes.Add: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Sub: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Mul: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Div: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Mod: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Not: StackValue.ExecuteUnaryOperator(code, cs); break;

               // case OPCodes.PrefixMinus: StackValue.ExecuteUnaryOperator(code, cs); break;

                case OPCodes.Or: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.And: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Xor: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Equal: StackValue.ExecuteBinaryOperator(code, cs); break;

               // case OPCodes.NotEqual: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Less: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.LessEqual: StackValue.ExecuteBinaryOperator(code, cs); break;

                case OPCodes.Call:
                    {
                        StackValue svFunc = cs.Pop();
                        Debug.Assert(svFunc.GetValueType() == Script.ValueType.Function);

                        //call function
                        {
                            Debug.Assert(cs.GetValueAt(0).GetValueType() == Alice.Script.ValueType.Arg);
                            // -1 for arg count, +1 for return value
                            Int64 expectedStackTop = cs.StackSize() - cs.GetValueAt(0).GetArg() - 1 + 1;
                            Int64 stackTopBefore = cs.StackSize();

                            IAliceFunction f = svFunc.GetFunction();
                            f.Call(cs);

                            if (cs.Sleep)
                            {
                                Debug.Assert(cs.StackSize() == stackTopBefore);
                                //restore everyting as it was before call
                                cs.Push(svFunc);
                                cs.CurrentIP--;
                            }
                            else
                            {
                                if (f is CSFunction)
                                {
                                    Debug.Assert(cs.StackSize() == expectedStackTop);
                                }

                                if (f is AliceFunction)
                                {
                                    Debug.Assert(cs.StackSize() == stackTopBefore + 1);
                                }
                            }

                           // Debug.Assert(cs.StackSize() == expectedStackTop);
                            //Push(CreateStackValueArg(m_IP));
                           // Push(CreateStackValueCodePtr(new AliceCodePointer(m_currentProgram, m_IP)));
                          //  m_IP = (Int32)svFunc.GetFunction().IP;
                           // m_currentProgram = svFunc.GetFunction().Program;
                          //  StackValue currentIP = new StackValue(new AliceFunction(new CodePointer(cs.CurrentProgram, cs.CurrentIP)));
                          //  cs.Push(ref currentIP);
                           // cs.CurrentIP = svFunc.
                        }
                     /*   else
                        {
                            StackValue32.ExecuteNativeFunction(svFunc, this, ref m_threadSleep, ref m_waitState);

                            //execute it again
                            if (m_threadSleep)
                            {
                                Push(CreateStackValueClone(svFunc));
                                m_IP--;
                            }
                        }

                        DestroyStackValue(svFunc);*/
                    }
                    break;

                case OPCodes.CallRet:
                    {
                        StackValue svFunctionDataCount = cs.Pop();
                        Debug.Assert(svFunctionDataCount.GetValueType() == Script.ValueType.Arg);

                        //pop return value
                        StackValue svReturnValue = cs.Pop();

                        //pop function data
                        for (int i = 0; i < svFunctionDataCount.GetArg(); i++)
                            cs.Pop();

                        //pop return address
                        StackValue svRetAddress = cs.Pop();
                        Debug.Assert(svRetAddress.GetValueType() == Script.ValueType.Function);

                        //pop function [args count] and [args]
                        StackValue svFuncArgCount = cs.Pop();
                        Debug.Assert(svFuncArgCount.GetValueType() == Script.ValueType.Arg);
                        for (int i = 0; i < svFuncArgCount.GetArg(); i++)
                            cs.Pop();

                        //push return and go to last code
                        cs.Push(ref svReturnValue);

                        AliceFunction af = (AliceFunction)svRetAddress.GetFunction();
                        Debug.Assert(af != null);

                        cs.CurrentIP = af.CodePointer.IP;
                        cs.CurrentProgram = af.CodePointer.Program;

                        if (af.CodePointer.Program == null)
                        {
                            Debug.Assert(cs.StackSize() == 1);
                            cs.CallFinished = true;
                            Debug.Assert(cs.Sleep == false && cs.SleepState.GetValueType() == Alice.Script.ValueType.Undefined);
                        }

                       // m_IP = (Int32)svRetAddress.GetFunction().IP;
                      //  m_currentProgram = svRetAddress.GetFunction().Program;

                      //  DestroyStackValue(svFuncArgCount);
                      //  DestroyStackValue(svRetAddress);
                     //   DestroyStackValue(svFunctionDataCount);
                    }
                    break;

                case OPCodes.LoadArg8:
                    cs.Push( StackValue.CreateArg(((sbyte)cs.CurrentProgram.ProgramCode[cs.CurrentIP])));
                    cs.CurrentIP++;
                    break;

                case OPCodes.LoadArg16:
                    cs.Push(StackValue.CreateArg(BitConverter.ToInt16(cs.CurrentProgram.ProgramCode, cs.CurrentIP)));
                    cs.CurrentIP += 2;
                    break;

                case OPCodes.LoadArg32:
                    cs.Push(StackValue.CreateArg(BitConverter.ToInt32(cs.CurrentProgram.ProgramCode, cs.CurrentIP)));
                    cs.CurrentIP += 4;
                    break;

              /*  case OPCodes.LoadArg64:
                    cs.Push(StackValue.CreateArg(BitConverter.ToInt64(cs.CurrentProgram.ProgramCode, (Int32)cs.CurrentIP)));
                    cs.CurrentIP += 8;
                    break;*/

                case OPCodes.LoadConst:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);
                        StackValue newSv;

                        Debug.Assert(svArg.GetArg() >= 0);

                        //load const value
                        Program.ConstValue cv = cs.CurrentProgram.ConstantValues[svArg.GetArg()];
                        if (cv.GetValueType() == Program.ConstValue.Type.NumericType)
                            newSv = StackValue.CreateNumeric(cv.NumericValue);
                        else
                            newSv = StackValue.CreateString(cv.StringValue);

                        cs.Push(newSv);
                    }
                    break;

                case OPCodes.LoadIP:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);

                        StackValue newSV = StackValue.CreateFunction(new AliceFunction(new CodePointer(cs.CurrentProgram, svArg.GetArg())));
                        cs.Push(newSV);
                    }
                    break;

                case OPCodes.LoadSpecial:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);
                        StackValue newSv;

                        //load special value                            
                        switch ((SpecialValues)svArg.GetArg())
                        {
                            case SpecialValues.Undefined: newSv = StackValue.CreateUndefined(); break;
                            case SpecialValues.Null: newSv = StackValue.CreateObject(null); break;
                            case SpecialValues.True: newSv = StackValue.CreateBool(true); break;
                            case SpecialValues.False: newSv = StackValue.CreateBool(false); break;
                            case SpecialValues.System: newSv = cs.Owner.System; break;
                            case SpecialValues.NewObject: newSv = StackValue.CreateObject(new AliceObject()); break;
                            default:
                                throw new Exception("wrong special value");
                        }

                        cs.Push(newSv);
                    }
                    break;

                case OPCodes.RunTimeError:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);
                        StackValue newSv;

                        //load special value                            
                        switch ((RuntimeError)svArg.GetArg())
                        {
                            case RuntimeError.WrongArgumentCount: throw new AliceRuntimeExceptionWringArgumentCount(0, 0); break;
                            default:
                                throw new Exception("wrong special value");
                        }


                        cs.Push(newSv);
                    }
                    break;

             /*   case OPCodes.DefineStackVar:
                    {
                        StackValue32 svArg = Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.String);

                        //TODO: save name for leater use (eval)
                        StackValue32 newSv = CreateStackValue();
                        newSv.SetObject(null);
                        Push(newSv);

                        DestroyStackValue(svArg);
                    }
                    break;*/

                case OPCodes.GetStackVar:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);

                        cs.Push(cs.GetValueAt(svArg.GetArg()));
                    }
                    break;

                case OPCodes.SetStackVar:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);
                                                
                        StackValue sv = cs.Pop();

                        cs.SetValueAt(svArg.GetArg(), ref sv);

                        cs.Push(sv);
                    }
                    break;

                case OPCodes.GetObjectMember:
                    {
                        StackValue svFieldName = cs.Pop();
                        Debug.Assert(svFieldName.GetValueType() == Script.ValueType.String);

                        StackValue svObject = cs.Pop();
                        Debug.Assert(svObject.GetValueType() == Script.ValueType.Object);

                        //do it
                        StackValue svValue = ((IAliceObject)svObject.GetObject()).GetField(svFieldName.GetString());

                        cs.Push(svValue);
                    }
                    break;

                case OPCodes.SetObjectMember:
                    {
                        StackValue svFieldName = cs.Pop();
                        Debug.Assert(svFieldName.GetValueType() == Script.ValueType.String);

                        StackValue svObject = cs.Pop();
                        Debug.Assert(svObject.GetValueType() == Script.ValueType.Object);

                        StackValue svValue = cs.Pop();
                        Debug.Assert(svValue.GetValueType() != Script.ValueType.Arg);

                        //do it
                        ((IAliceObject)svObject.GetObject()).SetField(svFieldName.GetString(), svValue);

                        cs.Push(svValue);
                    }
                    break;

               /* case OPCodes.LoadNamedVar:
                    {
                        StackValue32 svArg = Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.String);

                        //load var and push on top
                        StackValue32 svVar;

                        //try load shared
                        object sv;
                        if (m_sharedVars.GetValue(svArg.GetString(), out sv))
                        {
                            svVar = CreateStackValueFromDotNet(sv);
                        }
                        else
                        {
                            //find local
                            if (m_namedVars.TryGetValue(svArg.GetString(), out svVar))
                            {
                                svVar = CreateStackValueClone(svVar);
                            }
                            else
                            {
                                //create new if not found
                                svVar = CreateStackValue();
                                svVar.SetUndefined();
                            }
                        }

                        Push(svVar);

                        DestroyStackValue(svArg);
                    }
                    break;

                case OPCodes.StoreNamedVar:
                    {
                        StackValue32 svArg = Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.String);

                        //pop val and copy it into var
                        StackValue32 value = Pop();

                        //try use shared
                        if (m_sharedVars.ContainsValue(svArg.GetString()))
                        {
                            m_sharedVars.SetGlobalValue(svArg.GetString(), value.ToDotNetObject());
                            DestroyStackValue(value);
                        }
                        else
                        {
                            if (m_namedVars.ContainsKey(svArg.GetString()))
                            {
                                DestroyStackValue(m_namedVars[svArg.GetString()]);
                                m_namedVars[svArg.GetString()] = value;
                            }
                            else
                                m_namedVars.Add(svArg.GetString(), value);
                        }

                        DestroyStackValue(svArg);
                    }
                    break;*/

                case OPCodes.Pop:
                    cs.Pop();
                    break;

                case OPCodes.PopN:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);

                        //pop n vars
                        for (int i = 0; i < svArg.GetArg(); i++)
                            cs.Pop();

                        //DestroyStackValue(svArg);
                    }
                    break;

                case OPCodes.Jump:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);

                        //jump
                        cs.CurrentIP += svArg.GetArg();

                       // DestroyStackValue(svArg);
                    }
                    break;

                case OPCodes.JumpIfNotTrue:
                    {
                        StackValue svArg = cs.Pop();
                        Debug.Assert(svArg.GetValueType() == Script.ValueType.Arg);

                        //pop condition
                        StackValue svCondition = cs.Pop();
                        Debug.Assert(svCondition.GetValueType() == Script.ValueType.Bool);

                        //jump
                        if (StackValue.IsFalse(svCondition))
                            cs.CurrentIP += svArg.GetArg();

                      //  DestroyStackValue(svCondition);
                     //   DestroyStackValue(svArg);
                    }
                    break;
                default:
                    {
                        Debug.Assert(false, "you shouldnt be here");
                        throw new NotImplementedException();
                    }
            }
        }

       /* StackValue32 CreateStackValue()
        {
            m_stackVarCount++;
            StackValue32 sv = new StackValue32();
            sv.AddRef();
            return sv;
        }
        StackValue32 CreateStackValueClone(StackValue32 sv)
        {
            sv.AddRef();
            return sv;
        }
        StackValue32 CreateStackValueConst(Int32 v)
        {
            StackValue32 sv = CreateStackValue();

            Program.ConstValue cv = m_program.ConstantValues[v];
            if (cv.GetValueType() == Program.ConstValue.Type.NumericType)
                sv.SetNumeric(cv.NumericValue);
            else
                sv.SetString(cv.StringValue);

            return sv;
        }
        StackValue32 CreateStackValueArg(Int32 v)
        {
            StackValue32 sv = CreateStackValue();
            sv.SetArg(v);
            return sv;
        }
        StackValue32 CreateStackValueCodePtr(AliceCodePointer v)
        {
            StackValue32 sv = CreateStackValue();
            sv.SetFunction(v);
            return sv;
        }
        StackValue32 CreateStackValueFromDotNet(object v)
        {
            StackValue32 sv = CreateStackValue();
            sv.FromDotNetObject(v);
            return sv;
        }
        void DestroyStackValue(StackValue32 sv)
        {
            sv.Release();
            Debug.Assert(sv.ReferenceCount >= 0);
            if (sv.ReferenceCount == 0)
            {
                sv.CleanValue();
                m_stackVarCount--;
                //destroy
            }
        }

        StackValue32 StackValueAt(Int32 offset)
        {
            Debug.Assert(m_stack.Count > offset);
            return m_stack[offset];
        }
        StackValue32 Pop()
        {
            Debug.Assert(m_stack.Count > 0);
            StackValue32 sv = m_stack[m_stack.Count - 1];
            m_stack.RemoveAt(m_stack.Count - 1);
            return sv;
        }
        void Push(StackValue32 v)
        {
            m_stack.Add(v);
        }*/
    /*    void EndProgramClean()
        {
            m_threadSleep = true;
            m_programFinished = true;

            //clear stack
            while (m_stack.Count > 0)
                DestroyStackValue(Pop());

            //clear named vars
            foreach (var kvp in m_namedVars)
                DestroyStackValue(kvp.Value);
            m_namedVars.Clear();

            //check var count
            Debug.Assert(m_stackVarCount == 0);
        }

        bool m_threadSleep;
        object m_waitState;

        bool m_programFinished;

        Int32 m_IP;
        Program m_currentProgram;*/
       // readonly int[] m_opcodeStats;

       // SharedVariables m_sharedVars;
       // Program m_program;
      //  readonly List<StackValue32> m_stack = new List<StackValue32>();
       // readonly Dictionary<string, StackValue32> m_namedVars = new Dictionary<string, StackValue32>();
       // int m_stackVarCount = 0;

        #endregion
    }
}
